package demo.java.util.concurrent.locks;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;

import demo.java.lang.ThreadDemo;

/**
 * Java 8自带了一种新的锁，叫做StampedLock，它同样支持读写锁，就像上面的例子那样。与ReadWriteLock不同的是，StampedLock的锁方法会返回表示为long的标记。
 * 你可以使用这些标记来释放锁，或者检查锁是否有效。此外，StampedLock支持另一种叫做乐观锁（optimistic locking）的模式。
 * 
 * 通过readLock()或 writeLock()来获取读锁或写锁会返回一个标记，它可以在稍后用于在finally块中解锁。要记住StampedLock并没有实现重入特性。
 * 每次调用加锁都会返回一个新的标记，并且在没有可用的锁时阻塞，即使相同线程已经拿锁了。所以你需要额外注意不要出现死锁。
 * 
 */
public class StampedLockDemo {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Map<String, String> map = new HashMap<>();
        StampedLock lock = new StampedLock();

        executor.submit(() -> {
            long stamp = lock.writeLock();
            try {
                ThreadDemo.safeSleep(1);
                map.put("foo", "bar");
            } finally {
                lock.unlockWrite(stamp);
            }
        });

        Runnable readTask = () -> {
            long stamp = lock.readLock();
            try {
                System.out.println(map.get("foo"));
                ThreadDemo.safeSleep(1);
            } finally {
                lock.unlockRead(stamp);
            }
        };

        executor.submit(readTask);
        executor.submit(readTask);

        ThreadDemo.safeSleep(10_000L);
        executor.shutdown();
    }

    /**
     * 乐观的读锁通过调用tryOptimisticRead()获取，它总是返回一个标记而不阻塞当前线程，无论锁是否真正可用。
     * 如果已经有写锁被拿到，返回的标记等于0。你需要总是通过lock.validate(stamp)检查标记是否有效。
     * 
     * 乐观锁在刚刚拿到锁之后是有效的。和普通的读锁不同的是，乐观锁不阻止其他线程同时获取写锁。在第一个线程暂停一秒之后，第二个线程拿到写锁而无需等待乐观的读锁被释放。
     * 此时，乐观的读锁就不再有效了。甚至当写锁释放时，乐观的读锁还处于无效状态。
     * 
     * 所以在使用乐观锁时，你需要每次在访问任何共享可变变量之后都要检查锁，来确保读锁仍然有效。
     */
    static void demo2() {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        StampedLock lock = new StampedLock();

        executor.submit(() -> {
            long stamp = lock.tryOptimisticRead();
            try {
                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
                ThreadDemo.safeSleep(1);
                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
                ThreadDemo.safeSleep(2);
                System.out.println("Optimistic Lock Valid: " + lock.validate(stamp));
            } finally {
                lock.unlock(stamp);
            }
        });

        executor.submit(() -> {
            long stamp = lock.writeLock();
            try {
                System.out.println("Write Lock acquired");
                ThreadDemo.safeSleep(2);
            } finally {
                lock.unlock(stamp);
                System.out.println("Write done");
            }
        });

        ThreadDemo.safeSleep(10_000L);
        executor.shutdown();
    }

    /**
     * 有时，将读锁转换为写锁而不用再次解锁和加锁十分实用。StampedLock为这种目的提供了tryConvertToWriteLock()方法，
     * 
     * 第一个任务获取读锁，并向控制台打印count字段的当前值。但是如果当前值是零，我们希望将其赋值为23。我们首先需要将读锁转换为写锁，来避免打破其它线程潜在的并发访问。
     * tryConvertToWriteLock()的调用不会阻塞，但是可能会返回为零的标记，表示当前没有可用的写锁。这种情况下，我们调用writeLock()来阻塞当前线程，直到有可用的写锁。
     */
    static void demo3() {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        StampedLock lock = new StampedLock();

        executor.submit(() -> {
            int count = 0;
            long stamp = lock.readLock();
            try {
                if (count == 0) {
                    stamp = lock.tryConvertToWriteLock(stamp);
                    if (stamp == 0L) {
                        System.out.println("Could not convert to write lock");
                        stamp = lock.writeLock();
                    }
                    count = 23;
                }
                System.out.println(count);
            } finally {
                lock.unlock(stamp);
            }
        });

        ThreadDemo.safeSleep(10_000L);
        executor.shutdown();
    }
}
